using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using Framework.StringSplit;
using Framework.StringSplit.Processors;

namespace Framework
{
    public static class DelimitedUtil
    {
        #region Add

        public static string Add(string delimitedList, string addValue)
        {
            return Add(true, delimitedList, addValue, ',');
        }
        public static string Add(string delimitedList, string addValue, char seperator)
        {
            return Add(true, delimitedList, addValue, seperator);
        }

        /// <summary>
        /// Add a value to a delimited list of values if it does not already exist.
        /// </summary>
        /// <param name="trimDelimitedList">If true, each delimited element is trimmed of whitespace.</param>
        /// <param name="delimitedList">String to search in</param>
        /// <param name="addValue">String to search for</param>
        /// <param name="seperator">seperator to search for</param>
        /// <returns></returns>
        public static string Add(bool trimDelimitedList, string delimitedList, string addValue, char seperator)
        {
            var result = SplitAsList(trimDelimitedList ? StringSplitOptionsType.TrimWhitespace : StringSplitOptionsType.None, delimitedList, seperator);

            if (!Is.InList(addValue, result))
            {
                result.Add(addValue);
            }

            return Combine(result, seperator.ToString(CultureInfo.InvariantCulture));
        }
        #endregion

        #region Remove
        public static string Remove(string delimitedList, string removeValue)
        {
            return Remove(true, delimitedList, removeValue, ',');
        }
        public static string Remove(string delimitedList, string removeValue, char seperator)
        {
            return Remove(true, delimitedList, removeValue, seperator);
        }

        /// <summary>
        /// Remove a value from a delimited list of values if it exists.
        /// </summary>
        /// <param name="trimDelimitedList">If true, each delimited element is trimmed of whitespace.</param>
        /// <param name="delimitedList">String to search in</param>
        /// <param name="removeValue">String to search for</param>
        /// <param name="seperator">seperator to search for</param>
        /// <returns></returns>
        public static string Remove(bool trimDelimitedList, string delimitedList, string removeValue, char seperator)
        {
            var result = SplitAsList(trimDelimitedList ? StringSplitOptionsType.TrimWhitespace : StringSplitOptionsType.None, delimitedList, seperator);

            if (!Is.EmptyArray(result))
            {
                for (var i = 0; i < result.Count-1; i++)
                {
                    if (Is.EqualString(result[i], removeValue))
                    {
                        result.RemoveAt(i);
                        break;
                    }
                }
            }

            return Combine(result, seperator.ToString(CultureInfo.InvariantCulture));
        }
        #endregion

        #region Exists
        public static bool Exists(string searchIn, string searchFor)
        {
            return Exists(true, true, searchIn, searchFor, ',');
        }
        public static bool Exists(string searchIn, string searchFor, char delimiter)
        {
            return Exists(true, true, searchIn, searchFor, delimiter);
        }

        /// <summary>
        /// Check if a value exists in a delimited list of values, search is case insensitive. Return true if <var>searchFor</var> is found within <var>searchIn</var>
        /// </summary>
        /// <param name="trimSearchIn">If true, each delimited element is trimmed of whitespace.</param>
        /// <param name="trimSearchFor">If true, the value being searched for is trimmed of whitespace.</param>
        /// <param name="searchIn">String to search in</param>
        /// <param name="searchFor">String to search for</param>
        /// <param name="delimiter">Delimiter to search for</param>
        /// <returns></returns>
        public static bool Exists(bool trimSearchIn, bool trimSearchFor, string searchIn, string searchFor, char delimiter)
        {
            var list = Split(trimSearchIn ? StringSplitOptionsType.TrimWhitespace : StringSplitOptionsType.None, searchIn, delimiter);

            var searchValue = (searchFor == null ? String.Empty : (trimSearchFor ? searchFor.Trim() : searchFor));

            return list.Any(item => Is.EqualString(item, searchValue));
        }
        #endregion

        #region Combine
        /// <summary>
        /// Combines a string[], using a delimiter. Null strings are treated as String.Empty.
        /// </summary>
        /// <param name="values">Combine an array of strings using a delimiter to seperate</param>
        /// <returns>string</returns>
        public static string Combine(string[] values)
        {
            return Combine(values, ",", false);
        }

        public static string Combine(string[] values, string seperator)
        {
            return Combine(values, seperator, false);
        }

        /// <summary>
        /// Combines a string[], using a delimiter. Null strings are treated as String.Empty.
        /// </summary>
        /// <param name="values">Combine an array of strings using a delimiter to seperate</param>
        /// <param name="seperator">seperator to split on</param>
        /// <param name="suppressEmptyStrings"></param>
        /// <returns>string</returns>
        public static string Combine(string[] values, string seperator, bool suppressEmptyStrings)
        {
            if (Is.EmptyArray(values))
            {
                return String.Empty;
            }

            if (suppressEmptyStrings)
            {
                values = ArrayUtil.FilterEmptyStrings(values);

                if (Is.EmptyArray(values))
                {
                    return String.Empty;
                }
            }

            StringBuilder result;
            using (TextBuilder buffer = TextBuilder.Instance(out result, seperator))
            {

                for (int i = 0; i < values.Length; i++)
                {
                    if (i == values.Length - 1)
                    {
                        buffer.Write(StrUtil.DefaultValue(values[i]));
                    }
                    else
                    {
                        buffer.WriteLine(StrUtil.DefaultValue(values[i]));
                    }
                }
            }

            return result.ToString();
        }

        /// <summary>
        /// Combines a List<string/>, using a delimiter. Null strings are treated as String.Empty.
        /// </summary>
        /// <param name="values">Combine an array of strings using a delimiter to seperate</param>
        /// <returns>string</returns>
        public static string Combine(List<string> values)
        {
            return Combine(values, ",", false);
        }

        public static string Combine(List<string> values, string seperator)
        {
            return Combine(values, seperator, false);
        }

        /// <summary>
        /// Combines a List<string/>, using a delimiter. Null strings are treated as String.Empty.
        /// </summary>
        /// <param name="values">Combine an array of strings using a delimiter to seperate</param>
        /// <param name="seperator">seperator to split on</param>
        /// <param name="suppressEmptyStrings"></param>
        /// <returns>string</returns>
        public static string Combine(List<string> values, string seperator, bool suppressEmptyStrings)
        {
            return Combine(values.ToArray(), seperator, suppressEmptyStrings);
        }

        /// <summary>
        /// Combines a List<int/>, using a comma delimiter.
        /// </summary>
        /// <param name="values">Combine an array of ints using a delimiter to seperate</param>
        /// <returns>string</returns>
        public static string Combine(List<int> values)
        {
            return Combine(values, ",", false);
        }

        /// <summary>
        /// Combines a List<int/>, using a delimiter.
        /// </summary>
        /// <param name="values">The values.</param>
        /// <param name="seperator">The seperator.</param>
        /// <returns></returns>
        public static string Combine(List<int> values, string seperator)
        {
            return Combine(values, seperator, false);
        }

        /// <summary>
        /// Combines a List<int/>, using a delimiter. Null strings are treated as String.Empty.
        /// </summary>
        /// <param name="values">Combine an array of strings using a delimiter to seperate</param>
        /// <param name="seperator">seperator to split on</param>
        /// <param name="suppressEmptyStrings"></param>
        /// <returns>string</returns>
        public static string Combine(List<int> values, string seperator, bool suppressEmptyStrings)
        {
            if (Is.EmptyArray(values))
            {
                return Combine(new string[] { }, seperator, suppressEmptyStrings);
            }

            var stringValues = new string[values.Count];

            for (var i = 0; i < values.Count; i++)
            {
                stringValues[i] = values[i].ToString(CultureInfo.InvariantCulture);
            }

            return Combine(stringValues, seperator, suppressEmptyStrings);
        }
        #endregion

        #region Join
        /// <summary>
        /// Join two strings together with a seperator. Join ensures that there is only one seperator between the two strings
        /// </summary>
        /// <param name="stringLhs">String on left hand side</param>
        /// <param name="stringRhs">String on right hand side</param>
        /// <param name="seperator">seperator</param>
        /// <returns>LHS + Seperator + RHS</returns>
        public static string Join(string stringLhs, string stringRhs, string seperator)
        {
            var s1 = (stringLhs == null ? String.Empty : stringLhs.Trim());
            var s2 = (stringRhs == null ? String.Empty : stringRhs.Trim());
            var s1HasSeperator = s1.EndsWith(seperator);
            var s2HasSeperator = s2.StartsWith(seperator);

            var result = new StringBuilder(s1.Length + s2.Length + seperator.Length);

            if (s1HasSeperator && s2HasSeperator)
            {
                result.Append(s1);
                result.Append(s2.Substring(seperator.Length));
            }
            else if (s1HasSeperator || s2HasSeperator)
            {
                result.Append(s1);
                result.Append(s2);
            }
            else
            {
                result.Append(s1);
                result.Append(seperator);
                result.Append(s2);
            }

            return result.ToString();
        }
        #endregion

        #region Split

        /// <summary>
        /// Split a string into a string[], check 1st to see if the string is empty and if 
        /// it is then return an zero length array, string[0]. By default each element is trimmed of whitespace.
        /// </summary>
        /// <param name="text">String to split</param>
        /// <param name="seperator">seperator to split on</param>
        /// <returns>string[]</returns>
        public static string[] Split(string text, params char[] seperator)
        {
            return Fluent.StringSplit.
                Config.
                    SetSeperator(seperator).
                End.
                    Split<SplitAsStringArrayProcessor>(text);
        }

        /// <summary>
        /// Split a string into a string[], check 1st to see if the string is empty and if 
        /// it is then return an zero length array, string[0]. By default each element is trimmed of whitespace.
        /// </summary>
        /// <param name="text">String to split</param>
        /// <param name="seperator">seperator to split on</param>
        /// <returns>string[]</returns>
        public static string[] Split(string text, string[] seperator)
        {
            return Fluent.StringSplit.
                Config.
                    SetSeperator(seperator).
                End.
                    Split<SplitAsStringArrayProcessor>(text);
        }

        /// <summary>
        /// Split a string into a string[], check 1st to see if the string is empty and if 
        /// it is then return an zero length array, string[0]. By default each element is trimmed of whitespace.
        /// </summary>
        /// <param name="text">String to split</param>
        /// <param name="count"> </param>
        /// <param name="seperator">seperator to split on</param>
        /// <returns>string[]</returns>
        public static string[] Split(string text, int count, params char[] seperator)
        {
            return Fluent.StringSplit.
                Config.
                    SetSeperator(seperator).
                    SetCount(count).
                End.
                    Split<SplitAsStringArrayProcessor>(text);
        }

        /// <summary>
        /// Split a string into a string[], check 1st to see if the string is empty and if 
        /// it is then return an zero length array, string[0]. By default each element is trimmed of whitespace.
        /// </summary>
        /// <param name="text">String to split</param>
        /// <param name="count"> </param>
        /// <param name="seperator">seperator to split on</param>
        /// <returns>string[]</returns>
        public static string[] Split(string text, int count, string[] seperator)
        {
            return Fluent.StringSplit.
                Config.
                    SetSeperator(seperator).
                    SetCount(count).
                End.
                    Split<SplitAsStringArrayProcessor>(text);
        }

        /// <summary>
        /// Split a string into a string[], check 1st to see if the string is empty and if 
        /// it is then return an zero length array, string[0].
        /// </summary>
        /// <param name="options"> </param>
        /// <param name="text">String to split</param>
        /// <param name="seperator">seperator to split on</param>
        /// <returns>string[]</returns>
        public static string[] Split(StringSplitOptionsType options, string text, params char[] seperator)
        {
            return Fluent.StringSplit.
                Config.
                    SetSeperator(seperator).
                    SetOptions(options).
                End.
                    Split<SplitAsStringArrayProcessor>(text);
        }

        /// <summary>
        /// Split a string into a string[], check 1st to see if the string is empty and if 
        /// it is then return an zero length array, string[0].
        /// </summary>
        /// <param name="options"> </param>
        /// <param name="text">String to split</param>
        /// <param name="seperator">seperator to split on</param>
        /// <returns>string[]</returns>
        public static string[] Split(StringSplitOptionsType options, string text, string[] seperator)
        {
            return Fluent.StringSplit.
                Config.
                    SetSeperator(seperator).
                    SetOptions(options).
                End.
                    Split<SplitAsStringArrayProcessor>(text);
        }

        /// <summary>
        /// Split a string into a string[], check 1st to see if the string is empty and if 
        /// it is then return an zero length array, string[0].
        /// </summary>
        /// <param name="options"> </param>
        /// <param name="count"> </param>
        /// <param name="text">String to split</param>
        /// <param name="seperator">seperator to split on</param>
        /// <returns>string[]</returns>
        public static string[] Split(StringSplitOptionsType options, int count, string text, params char[] seperator)
        {
            return Fluent.StringSplit.
                Config.
                    SetSeperator(seperator).
                    SetOptions(options).
                    SetCount(count).
                End.
                    Split<SplitAsStringArrayProcessor>(text);
        }

        /// <summary>
        /// Split a string into a string[], check 1st to see if the string is empty and if 
        /// it is then return an zero length array, string[0].
        /// </summary>
        /// <param name="options"> </param>
        /// <param name="count"> </param>
        /// <param name="text">String to split</param>
        /// <param name="seperator">seperator to split on</param>
        /// <returns>string[]</returns>
        public static string[] Split(StringSplitOptionsType options, int count, string text, string[] seperator)
        {
            return Fluent.StringSplit.
                Config.
                    SetSeperator(seperator).
                    SetOptions(options).
                    SetCount(count).
                End.
                    Split<SplitAsStringArrayProcessor>(text);
        }
        #endregion

        #region SplitAsList
        /// <summary>
        /// Split a string into a List&lt;string&rt;, check 1st to see if the string is empty and if 
        /// it is then return an zero length array, string[0]. By default each element is trimmed of whitespace.
        /// </summary>
        /// <param name="text">String to split</param>
        /// <param name="seperator">seperator to split on</param>
        /// <returns>List&lt;string&rt;</returns>
        public static List<string> SplitAsList(string text, params char[] seperator)
        {
            return Fluent.StringSplit.
                Config.
                    SetSeperator(seperator).
                End.
                    SplitAsList<SplitAsStringList>(text);
        }

        /// <summary>
        /// Split a string into a List&lt;string&rt;, check 1st to see if the string is empty and if 
        /// it is then return an zero length array, string[0]. By default each element is trimmed of whitespace.
        /// </summary>
        /// <param name="text">String to split</param>
        /// <param name="count">No of return values to limit the splitting too </param>
        /// <param name="seperator">seperator to split on</param>
        /// <returns>List&lt;string&rt;</returns>
        public static List<string> SplitAsList(string text, int count, params char[] seperator)
        {
            return Fluent.StringSplit.
                Config.
                    SetSeperator(seperator).
                    SetCount(count).
                End.
                    SplitAsList<SplitAsStringList>(text);
        }

        /// <summary>
        /// Split a string into a List&lt;string&rt;, check 1st to see if the string is empty and if 
        /// it is then return an zero length array, string[0]. By default each element is trimmed of whitespace.
        /// </summary>
        /// <param name="text">String to split</param>
        /// <param name="seperator">seperator to split on</param>
        /// <returns>List&lt;string&rt;</returns>
        public static List<string> SplitAsList(string text, string[] seperator)
        {
            return Fluent.StringSplit.
                Config.
                    SetSeperator(seperator).
                End.
                    SplitAsList<SplitAsStringList>(text);
        }

        /// <summary>
        /// Split a string into a List&lt;string&rt;, check 1st to see if the string is empty and if 
        /// it is then return an zero length array, string[0]. By default each element is trimmed of whitespace.
        /// </summary>
        /// <param name="text">String to split</param>
        /// <param name="count">No of return values to limit the splitting too </param>
        /// <param name="seperator">seperator to split on</param>
        /// <returns>List&lt;string&rt;</returns>
        public static List<string> SplitAsList(string text, int count, string[] seperator)
        {
            return Fluent.StringSplit.
                Config.
                    SetSeperator(seperator).
                    SetCount(count).
                End.
                    SplitAsList<SplitAsStringList>(text);
        }

        /// <summary>
        /// Split a string into a List&lt;string&rt;, check 1st to see if the string is empty and if 
        /// it is then return an zero length array, string[0].
        /// </summary>
        /// <param name="options"> </param>
        /// <param name="text">String to split</param>
        /// <param name="seperator">seperator to split on</param>
        /// <returns>List&lt;string&rt;</returns>
        public static List<string> SplitAsList(StringSplitOptionsType options, string text, params char[] seperator)
        {
            return Fluent.StringSplit.
                Config.
                    SetSeperator(seperator).
                    SetOptions(options).
                End.
                    SplitAsList<SplitAsStringList>(text);
        }

        /// <summary>
        /// Split a string into a List&lt;string&rt;, check 1st to see if the string is empty and if 
        /// it is then return an zero length array, string[0].
        /// </summary>
        /// <param name="options"> </param>
        /// <param name="text">String to split</param>
        /// <param name="count">No of return values to limit the splitting too </param>
        /// <param name="seperator">seperator to split on</param>
        /// <returns>List&lt;string&rt;</returns>
        public static List<string> SplitAsList(StringSplitOptionsType options, string text, int count, params char[] seperator)
        {
            return Fluent.StringSplit.
                Config.
                    SetSeperator(seperator).
                    SetOptions(options).
                    SetCount(count).
                End.
                    SplitAsList<SplitAsStringList>(text);
        }

        /// <summary>
        /// Split a string into a List&lt;string&rt;, check 1st to see if the string is empty and if 
        /// it is then return an zero length array, string[0].
        /// </summary>
        /// <param name="options"> </param>
        /// <param name="text">String to split</param>
        /// <param name="seperator">seperator to split on</param>
        /// <returns>List&lt;string&rt;</returns>
        public static List<string> SplitAsList(StringSplitOptionsType options, string text, string[] seperator)
        {
            return Fluent.StringSplit.
                Config.
                    SetSeperator(seperator).
                    SetOptions(options).
                End.
                    SplitAsList<SplitAsStringList>(text);
        }

        /// <summary>
        /// Split a string into a List&lt;string&rt;, check 1st to see if the string is empty and if 
        /// it is then return an zero length array, string[0].
        /// </summary>
        /// <param name="options"> </param>
        /// <param name="text">String to split</param>
        /// <param name="count">No of return values to limit the splitting too </param>
        /// <param name="seperator">seperator to split on</param>
        /// <returns>List&lt;string&rt;</returns>
        public static List<string> SplitAsList(StringSplitOptionsType options, string text, int count, string[] seperator)
        {
            return Fluent.StringSplit.
                Config.
                    SetSeperator(seperator).
                    SetOptions(options).
                    SetCount(count).
                End.
                    SplitAsList<SplitAsStringList>(text);
        }
        #endregion

        #region SplitEscaped
        public static string[] SplitEscaped(string text, params char[] seperator)
        {
            return Fluent.StringSplit.
                Config.
                    SetSeperator(seperator).
                End.
                    Split<SplitEscapedAsStringArrary>(text);
        }

        public static string[] SplitEscaped(char esc, string text, params char[] seperator)
        {
            return Fluent.StringSplit.
                Config.
                    SetSeperator(seperator).
                    SetEscape(esc).
                End.
                    Split<SplitEscapedAsStringArrary>(text);
        }

        public static string[] SplitEscaped(string text, int count, params char[] seperator)
        {
            return Fluent.StringSplit.
                Config.
                    SetSeperator(seperator).
                    SetCount(count).
                End.
                    Split<SplitEscapedAsStringArrary>(text);
        }

        public static string[] SplitEscaped(char esc, string text, int count, params char[] seperator)
        {
            return Fluent.StringSplit.
                Config.
                    SetSeperator(seperator).
                    SetCount(count).
                    SetEscape(esc).
                End.
                    Split<SplitEscapedAsStringArrary>(text);
        }

        public static string[] SplitEscaped(char esc, StringSplitOptionsType options, string text, params char[] seperator)
        {
            return Fluent.StringSplit.
                Config.
                    SetSeperator(seperator).
                    SetOptions(options).
                    SetEscape(esc).
                End.
                    Split<SplitEscapedAsStringArrary>(text);
        }

        public static string[] SplitEscaped(char esc, StringSplitOptionsType options, string text, int count, params char[] seperator)
        {
            return Fluent.StringSplit.
                Config.
                    SetSeperator(seperator).
                    SetOptions(options).
                    SetCount(count).
                    SetEscape(esc).
                End.
                    Split<SplitEscapedAsStringArrary>(text);
        }
        #endregion

        #region SplitEscapedAsList
        public static List<string> SplitEscapedAsList(string text, params char[] seperator)
        {
            return Fluent.StringSplit.
                Config.
                    SetSeperator(seperator).
                End.
                    SplitAsList<SplitEscapedAsStringListProcessor>(text);
        }

        public static List<string> SplitEscapedAsList(char esc, StringSplitOptionsType options, string text, params char[] seperator)
        {
            return Fluent.StringSplit.
                Config.
                    SetSeperator(seperator).
                    SetOptions(options).
                    SetEscape(esc).
                End.
                    SplitAsList<SplitEscapedAsStringListProcessor>(text);
        }
        #endregion

        #region SplitKeyValueAsList
        public static List<KeyValuePair<string, string>> SplitKeyValueAsList(string text, char seperator)
        {
            return Fluent.StringSplit.
                Config.
                    SetSeperator(seperator).
                End.
                    SplitAsKeyValuePairList<SplitAsKeyValueListProcessor>(text);
        }

        public static List<KeyValuePair<string, string>> SplitKeyValueAsList(string text, char outerSeperator, char innerSeperator)
        {
            return Fluent.StringSplit.
                Config.
                    SetSeperator(outerSeperator).
                    SetSeperatorInner(innerSeperator).
                End.
                    SplitAsKeyValuePairList<SplitAsKeyValueListProcessor>(text);
        }
        #endregion

        #region SplitEscapedKeyValueAsList
        public static List<KeyValuePair<string, string>> SplitEscapedKeyValueAsList(string text, char seperator)
        {
            return Fluent.StringSplit.
                Config.
                    SetSeperator(seperator).
                End.
                    SplitAsKeyValuePairList<SplitEscapedAsKeyValueListProcessor>(text);
        }

        public static List<KeyValuePair<string, string>> SplitEscapedKeyValueAsList(char esc, string text, char seperator)
        {
            return Fluent.StringSplit.
                Config.
                    SetSeperator(seperator).
                    SetEscape(esc).
                End.
                    SplitAsKeyValuePairList<SplitEscapedAsKeyValueListProcessor>(text);
        }

        public static List<KeyValuePair<string, string>> SplitEscapedKeyValueAsList(string text, char outerSeperator, char innerSeperator)
        {
            return Fluent.StringSplit.
                Config.
                    SetSeperator(outerSeperator).
                    SetSeperatorInner(innerSeperator).
                End.
                    SplitAsKeyValuePairList<SplitEscapedAsKeyValueListProcessor>(text);
        }

        public static List<KeyValuePair<string, string>> SplitEscapedKeyValueAsList(char escOuter, char escInner, string text, char seperatorOuter, char seperatorInner)
        {
            return Fluent.StringSplit.
                Config.
                    SetSeperator(seperatorOuter).
                    SetSeperatorInner(seperatorInner).
                    SetEscape(escOuter).
                    SetEscapeInner(escInner).
                End.
                    SplitAsKeyValuePairList<SplitEscapedAsKeyValueListProcessor>(text);
        }
        #endregion

    }
}
